const 斷言 & infer 關鍵字


Posted by TempuraEngineer on 2023-11-17

目錄


const斷言是什麼

(2024/4/30更新)

const斷言(assertion)是一種型別斷言,它可以告訴Typescript限縮變數的型別、把變數變成readonly,寫immutable code時會用到

使用as const會把型別變成literal type

literal
adjective
The literal meaning of a word is its original, basic meaning.

literal type是一種鎖死的型別,簡單說就是你詳細定義「這個字串(數值)是什麼」、「這個陣列內的元素是什麼」、「這個物件內有什麼屬性、屬性值是什麼」

const斷言和const宣告的差別在於並不是用於宣告變數,或者限縮型別,兩者使用的場景並不完全一樣

 // 型別是string;
let fruit = 'apple';

 // 型別是apple;
let fruit2 = 'apple' as const;
const fruit3 = 'apple';
let arr = [10, 20]; // number[]
const  arr2 = [10, 20] as const; // readonly [10, 20]
const  arr3 = arr2; // readonly [10, 20]

arr.push(30);

// Property 'push' does not exist on type 'readonly [10, 20]'
arr2.push(30);

// Property 'push' does not exist on type 'readonly [10, 20]'
// 傳址,且其reference已經被as const鎖死型別,所以不能push
arr3.push(30);
const obj = {
    a:1,
};
// {
//     a: number;
// }

// 使用了as const以後物件的型別完全被鎖死,所有屬性變readonly(immutable)
const obj2 = {
    a:1,
} as const;
// {
//     readonly a: 1;
// }

// Property 'b' does not exist on type '{ a: number; }'.
obj.b = 2;
// 型別沒有被完全鎖死,所以可以修改屬性值
obj.a = 20;

Property 'b' does not exist on type '{ readonly a: 1; }'
obj2.b = 2;
Cannot assign to 'a' because it is a read-only property.
obj2.a = 20;

在一些需要enum的場景也可以使用const斷言代替

const mainDishes = {
    beef:'steak',
    pork:'pepper pork',
    lamb:'roasted lamb',
    fish:'sashimi',
    shrimp:'curry shirmp',
    carb:'butter crab',
} as const;
// {
//     readonly beef: "steak";
//     readonly pork: "pepper pork";
//     readonly lamb: "roasted lamb";
//     readonly fish: "sashimi";
//     readonly shrimp: "curry shirmp";
//     readonly carb: "butter crab";
// }

// Cannot assign to 'beef' because it is a read-only property.
mainDishes.beef = 'roasted beef';


const斷言限制

1.只能用於直接賦值宣告、簡單的表達式

```js
// bad
// A 'const' assertion can only be applied to a to a string, number, boolean, array, or object literal.
const num = 1 + 2 as const;
const day = new Date('2023-11-13') as const;

// ok
const num = 1 as const;
const set = new Set([1,2,3]);
const maxNum = Math.max(...[10,20,30]);
```

2.如果物件的屬性有reference,那const斷言不會對那個屬性生效

```js
let arr = ['chocolate']; // string[]
let arr2 = ['coffe'] as const; // let arr2 = ['coffe'] as const;

let person = {
  name: "Derek",
  favorite: arr,
  hate: arr2,
  hobby: ['gardening'],

} as const;
// {
//     readonly name: "Derek";
//     readonly favorite: string[];
//     readonly hobby: readonly ["gardening"];
// }

// Property 'push' does not exist on type 'readonly ["gardening"]'.
person.hobby.push('swimming');

// 可以push的原因是arr並沒有被用as const鎖死型別,所以可以修改
person.favorite.push('strawberry');

// Cannot assign to 'favorite' because it is a read-only property.
// 不可以重新將favorite屬性賦值,因為person被用as const鎖死型別
person.favorite = ['apple'];

// Property 'push' does not exist on type 'readonly ["coffe"]'.
// 不可以push是因為arr2被用as const鎖死型別
person.hate.push('milk');
```


infer關鍵字是什麼

使用infer關鍵字(keyword)可以讓TS自動從推斷型別參數推斷出一個型別,並傳給變數,可以在限縮型別的同時讓型別變得更彈性

用於條件型別(conditional type),能解決深度巢狀的條件型別(a? b : c? d : e? f : g)

enum Desserts {
    Cake = 'chocolate cake',
    Jelly = 'organge jelly',
    IceCream = 'macha ice cream'
}

// 攤平陣列取得element的型別
type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;

// Desserts[]是型別參數
// Type被extends限制必須要是陣列,如果Type是陣列,TS會從它推斷出其中的元素的型別,即Desserts,並將Desserts賦予給Item變數
type Sweets = Flatten<Desserts[]>; // Desserts

用於推斷return value型別、parameter型別

type GetReturnType<Type> = Type extends (...args: never[]) => infer Return ? Return : never;

type Num = GetReturnType<() => number>;

// 相當於
type Num2 = ReturnType<() => number>;
type GetParameterType<Type> = Type extends (arg: infer Args) => any ? Args : Type;

type Args = GetParameterType<(a:number, b: string) => void>

// 相當於
type Params = Parameters<(a:number, b: string) => void>;

React也提供了util type,只要傳component給它就可以取得其props型別

type ComponentProps<
  T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>
> = T extends JSXElementConstructor<infer P> ? P : 
      T extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[T] : {};

type MyButtonProps = ComponentProps<typeof MyButton>;

type MyDivProps = ComponentProps<"div"> & {
  customProperty: string;
};


infer關鍵字限制

不是conditional type時,infer不能寫在extend後(型別參數的限制子句)

// bad
// 'infer' declarations are only permitted in the 'extends' clause of a conditional type.
type Item<T extends (infer U)[]> = U;


參考資料

TS - const assertions
TS - Inferring Within Conditional Types
TS - Literal Types
TS - readonly Tuple Types


#TypeScript #const assertion #infer keyword #conditional type







Related Posts

第 19 期 Python 程式設計入門-作業任務5

第 19 期 Python 程式設計入門-作業任務5

ASP.NET Core Web API 入門教學 - JWT身分驗證

ASP.NET Core Web API 入門教學 - JWT身分驗證

Spring @PathVariable vs @PathParam

Spring @PathVariable vs @PathParam


Comments